﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace Interpreter
{
    /// <summary>
    /// String.concat(...)
    /// Concats all arguments into a string
    /// Return Value: The string concated by all params from left to right
    /// Param Count: 0..*
    /// Param Type : Number or string
    /// </summary>
    class string_concat
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            StringBuilder sb = new StringBuilder();
            Variable v;
            for (int i = 1; i <= numParams; ++i)
            {
                v = m.GetLocalByID(i);
                switch (v.type)
                {
                    case VarType.TYPE_NUMBER:
                        sb.Append(v.num);
                        break;
                    case VarType.TYPE_STRING:
                        sb.Append(v.str);
                        break;
                    default:
                        throw new RuntimeException("Invaild argument type");
                }
            }
            return new Variable(sb.ToString());
        }
    }

    /// <summary>
    /// String.from(var)
    /// Convert a number to a string, if var is already a string, do nothing 
    /// Return Value : Converted string
    /// Param [var]: A number or a string to be converted to a string
    /// </summary>
    class string_from
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            StringBuilder sb = new StringBuilder();
            if (numParams != 1)
                throw new RuntimeException("Invalid arguments");
            switch (m.GetLocalByID(1).type)
            {
                case VarType.TYPE_NUMBER:
                    sb.Append(m.GetLocalByID(1).num);
                    break;
                case VarType.TYPE_STRING:
                    sb.Append(m.GetLocalByID(1).str);
                    break;
                default:
                    throw new RuntimeException("Invaild argument type");
            }
            return new Variable(sb.ToString());
        }
    }

    /// <summary>
    /// String.length(string)
    /// string:length()
    /// Returns the length of a string
    /// Return Value: length of given string
    /// Param [string]: the string of which length to be calculated
    /// </summary>
    class string_length
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams != 1)
                throw new RuntimeException("Invalid arguments");

            return new Variable((double)m.GetLocalByID(1).str.Length);
            
        }
    }

    /// <summary>
    /// String.compare(string1,string2)
    /// string1:compare(string2)
    /// Compare two strings
    /// Return Value: 1 if two strings are identical, 0 otherwise
    /// Param [string1,string2]: Two strings to be compared
    /// </summary>
    class string_compare
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_STRING
                || m.GetLocalByID(2).type != VarType.TYPE_STRING)
                throw new RuntimeException("Invaild arguments");
            if (m.GetLocalByID(1).str == m.GetLocalByID(2).str)
                return new Variable(1.0);
            else
                return new Variable(0.0);
        }
    }

    /// <summary>
    /// String.contain(string1,string2)
    /// string1:contain(string2)
    /// To see if a string contains another string
    /// Return Value: 1 if string1 contains string2, 0 otherwise
    /// Param [string1]: The string to see if contains another string
    /// Param [string2]: The string to see if be contained in another string
    /// </summary>
    class string_contain
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_STRING
                || m.GetLocalByID(2).type != VarType.TYPE_STRING)
                throw new RuntimeException("Invaild arguments");
            if (m.GetLocalByID(1).str.Contains(m.GetLocalByID(2).str))
                return new Variable(1.0);
            else
                return new Variable(0.0);
        }
    }

    /// <summary>
    /// String.match(string,pattern,[result])
    /// string:match(pattern,[result])
    /// Match a string with given Reguear pattern
    /// Retuan Value: Number of string pieces matched
    /// Param [string] : The string to be matched
    /// Param [pattern] : A Reguear pattern
    /// Param [result] : Optional. If a table Variable is given, Stores matched string piece by following structure:
    ///                 result[0] : A table value, contain information of first matched string piece
    ///                     result[0][0] : A string value, the first matched string piece
    ///                     result[0][1] : A string value, The first captured string of first matched string piece,if any
    ///                     ...
    ///                     result[0][n] : The last captured string
    ///                 result[1] : A table value, contain information of second matched string piece
    ///                     ...
    ///                 ...
    ///                 result[n] : A table value, contain information of last matched string piece
    /// </summary>
    class string_match
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_STRING
                || m.GetLocalByID(2).type != VarType.TYPE_STRING)
                throw new RuntimeException("Invaild arguments");
            Regex r = new Regex(m.GetLocalByID(2).str);
            var match = r.Matches(m.GetLocalByID(1).str, 0);
            // Match returns number of parrerns matched
            // All capture values are stored at a table optional at param 3
            if (numParams >= 3 && m.GetLocalByID(3).type == VarType.TYPE_TABLE)
            {
                var table = m.GetLocalByID(3).table;
                int i = 0; int j = 0;
                foreach (Match item in match)
                {
                    table[new Variable((double)i)] = new Variable(new Dictionary<Variable, Variable>(VarCompare.GetInstance()));
                    j = 0; var currentItem = table[new Variable((double)i)].table;
                    foreach (Group g in item.Groups)
                    {
                        currentItem[new Variable((double)j)] = new Variable(g.Value);
                        ++j;
                    }
                    ++i;
                }
            }
            return new Variable((double)match.Count);
        }
    }

    /// <summary>
    /// String.sub(string,startIndex,[length])
    /// string:sub(stringIndex,[length])
    /// Return substring of a given string, the substring starts from a given position, and have a given length if specifed
    /// Retuan Vaule: a substring of a given string
    /// Param [string] : The source string
    /// Param [startIndex] : A number value, specify the given position
    /// Param [length] : Optional. A number value, specify the length of substring.If not specified, returns the whole substring from startIndex
    /// </summary>
    class string_sub
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_STRING ||
                m.GetLocalByID(2).type != VarType.TYPE_NUMBER )
                throw new RuntimeException("Invaild argument");
            if (numParams > 2 && m.GetLocalByID(3).type == VarType.TYPE_NUMBER)
                return new Variable(m.GetLocalByID(1).str.Substring((int)m.GetLocalByID(2).num, (int)m.GetLocalByID(3).num));
            else
                return new Variable(m.GetLocalByID(1).str.Substring((int)m.GetLocalByID(2).num));
        }
    }

    /// <summary>
    /// Math.sin(angle)
    /// Calculate the sin value of a given angle
    /// Return value : A number value, sin value of given angle
    /// Param [angle] : A number value, angle to be calculated
    /// </summary>
    class math_sin
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invaild arugment");
            return new Variable(System.Math.Sin(m.GetLocalByID(1).num / 360 * (2 * System.Math.PI)));
        }
    }

    /// <summary>
    /// Math.cos(angle)
    /// Calculate the cos value of a given angle
    /// Return value : A number value, cos value of given angle
    /// Param [angle] : A number value, angle to be calculated
    /// </summary>
    class math_cos
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invaild arugment");
            return new Variable(System.Math.Cos(m.GetLocalByID(1).num / 360 * (2 * System.Math.PI)));
        }
    }

    /// <summary>
    /// Math.abs(number)
    /// Calculate the absolute value of a given number
    /// Return value : A number value, absolute value of given number
    /// Param [number] : number to be calculated
    /// </summary>
    class math_abs
   : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invaild arugment");
            return new Variable(System.Math.Abs(m.GetLocalByID(1).num));
        }
    }

    /// <summary>
    /// Math.abs(number)
    /// Calculate the smallest integer greater than a given number
    /// Return value : A number value, the smallest integer greater than a given number
    /// Param [number] : number to be calculated
    /// </summary>
    class math_ceiling
   : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invaild arugment");
            return new Variable(System.Math.Ceiling(m.GetLocalByID(1).num));
        }
    }

    /// <summary>
    /// Math.floor(number)
    /// Calculate the biggest integer lesser than a given number
    /// Return value : A number value, the biggest integer lesser than a given number
    /// Param [number] : number to be calculated
    /// </summary>
    class math_floor
   : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invaild arugment");
            return new Variable(System.Math.Floor(m.GetLocalByID(1).num));
        }
    }

    /// <summary>
    /// Math.exp(number)
    /// Returns e raised to the specified power.
    /// Return value : A number value, e raised to the specified power
    /// Param [number] : A number specifying a power
    /// </summary>
    class math_exp
   : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invaild arugment");
            return new Variable(System.Math.Exp(m.GetLocalByID(1).num));
        }
    }

    /// <summary>
    /// Math.exp(x,y)
    /// Returns a specified number raised to the specified power.
    /// Return value : A number value, the specified number raised to the specified power.
    /// Param [x] : A number value, the number to be raised to a power. 
    /// Param [y] : A number value, the number that specifies a power. 
    /// </summary>
    class math_pow
   : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_NUMBER || m.GetLocalByID(2).type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invaild arugment");
            return new Variable(System.Math.Pow(m.GetLocalByID(1).num, m.GetLocalByID(2).num));
        }
    }

    /// <summary>
    /// Math.log(number,base)
    /// Returns the base e logarithm of a specified number.
    /// Return value : A number value, the base b logarithm of the specified number.
    /// Param [number] : A number whose logarithm is to be found.
    /// Param [base] : A number value, the base b.
    /// </summary>
    class math_log
   : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_NUMBER || m.GetLocalByID(2).type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invaild arugment");
            return new Variable(System.Math.Log(m.GetLocalByID(1).num, m.GetLocalByID(2).num));
        }
    }

    /// <summary>
    /// Math.random()
    /// Returns a nonnegative random number.
    /// Return value: A nonnegative integer less than 2,147,483,647
    /// Math.random(max)
    /// Returns a nonnegative random number less than the specified maximum.
    /// Param [max] : A nonnegative number value, the exclusive upper bound of the random number to be generated. 
    /// Math.random(min,max)
    /// Returns a random number within a specified range.
    /// Retuan value: A integer greater than or equal to minValue and less than maxValue; that is, the range of return values includes minValue but not maxValue. If minValue equals maxValue, minValue is returned.
    /// Param [min] : The inclusive lower bound of the random number returned
    /// Param [max] : The exclusive upper bound of the random number returned. max must be greater than or equal to min
    /// </summary>
    class math_random
    : IFunction
    {
        static Random random = new Random();
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams == 0)
            {
                return new Variable((double)random.Next());
            }
            else if (numParams == 1)
            {
                if (m.GetLocalByID(1).type != VarType.TYPE_NUMBER)
                    throw new RuntimeException("Invaild arugment");
                return new Variable((double)random.Next((int)m.GetLocalByID(1).num));
            }
            else if (numParams == 2)
            {
                if (m.GetLocalByID(1).type != VarType.TYPE_NUMBER || m.GetLocalByID(2).type != VarType.TYPE_NUMBER)
                    throw new RuntimeException("Invaild arugment");
                return new Variable((double)random.Next((int)m.GetLocalByID(1).num, (int)m.GetLocalByID(2).num));
            }
            else
                throw new RuntimeException("Invaild arugment");
        }
    }

    /// <summary>
    /// Table.copy(table)
    /// talbe:copy()
    /// Copys a table and its non-table elements
    /// Retuan Value: the copied table
    /// Param [table] : The table to be copied.
    /// </summary>
    class table_copy
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_TABLE)
                throw new RuntimeException("Invaild argument");
            Variable newTable = new Variable(new Dictionary<Variable,Variable>(VarCompare.GetInstance()));
            var table = newTable.table;
            foreach (var kvp in m.GetLocalByID(1).table)
            {
                table.Add(kvp.Key,kvp.Value);
            }
            return newTable;
        }
    }

    /// <summary>
    /// Table.insert(table,key,value)
    /// talbe:copy(key,value)
    /// Insert a key/value into table
    /// Return Value: 0
    /// Param [table] : The table to be operated
    /// Param [key] : Specify the key
    /// Param [value] : Specify the value corresponding to key
    /// </summary>
    class table_insert
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 3 || m.GetLocalByID(1).type != VarType.TYPE_TABLE)
                throw new RuntimeException("Invaild argument");
            m.GetLocalByID(1).table[m.GetLocalByID(2)] = m.GetLocalByID(3);
            return new Variable(0.0);
        }
    }

    /// <summary>
    /// Table.contain_key(table,key)
    /// table:contain_key(key)
    /// To see if the table contains a given key
    /// Return Value : 1 if the table contans the given key, 0 otherwise
    /// Param [table] : The table to be operated
    /// Param [key] : Specify the key
    /// </summary>
    class table_contain_key
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_TABLE)
                throw new RuntimeException("Invaild argument");
            if (m.GetLocalByID(1).table.ContainsKey(m.GetLocalByID(2)))
                return new Variable(1.0);
            else
                return new Variable(0.0);
        }
    }

    /// <summary>
    /// Table.contain_value(table,value)
    /// table:contain_value(value)
    /// To see if the table contains a given value
    /// Return Value : 1 if the table contans the given value, 0 otherwise
    /// Param [table] : The table to be operated
    /// Param [value] : Specify the value
    /// </summary>
    class table_contain_value
        : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_TABLE)
                throw new RuntimeException("Invaild argument");
            if (m.GetLocalByID(1).table.ContainsValue(m.GetLocalByID(2)))
                return new Variable(1.0);
            else
                return new Variable(0.0);
        }
    }

    /// <summary>
    /// Table.iterator(table)
    /// table:iterator()
    /// Return a iterator function for a tale pointing at before the first element
    /// Param [table] : The table to be operated
    /// </summary>
    class table_iterator
        : IFunction
    {
        /// <summary>
        /// Iterator
        /// table's iterator function
        /// Params: none
        /// Move the iterator to next element
        /// Return value: A table containing key/value pair as (.key .value) of current element after moving if not pointing after the last element
        ///                 0 if pointing after the last element
        /// </summary>
        class Iterator
            : IFunction
        {
            private IEnumerator<KeyValuePair<Variable, Variable>> m_enumerator;
            private static readonly Variable key = new Variable("key");
            private static readonly Variable value = new Variable("value");
            private Dictionary<Variable, Variable> m_table;
            public Iterator(IEnumerator<KeyValuePair<Variable, Variable>> enumerator)
            {
                m_enumerator = enumerator;
                m_table = new Dictionary<Variable, Variable>(VarCompare.GetInstance());
            }
            public Variable invoke(Machine m, int numParams)
            {
                if (m_enumerator.MoveNext())
                {
                    m_table[key] = m_enumerator.Current.Key;
                    m_table[value] = m_enumerator.Current.Value;
                    return new Variable(m_table);
                }
                else
                    return new Variable(0.0);
            }
        }

        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_TABLE)
                throw new RuntimeException("Invaild argument");
            return new Variable(new Iterator(m.GetLocalByID(1).table.GetEnumerator()));
        }
    }

    /// <summary>
    /// Table.remove(table,key)
    /// table:remove(key)
    /// Remove a key/value pair from given table
    /// Return Value : 1 if the table contans the given key, 0 otherwise
    /// Param [table] : The table to be operated
    /// Param [key] : Specify the key
    /// </summary>
    class table_remove
       : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_TABLE)
                throw new RuntimeException("Invaild argument");
            if (m.GetLocalByID(1).table.ContainsKey(m.GetLocalByID(2)))
            {
                m.GetLocalByID(1).table.Remove(m.GetLocalByID(2));
                return new Variable(1.0);
            }
            else
                return new Variable(0.0);
        }
    }

    /// <summary>
    /// Table.union(table1,table2)
    /// table1:union(table2)
    /// Union two tables, if a key exists in both table, the return table contains value from [table1]
    /// Return Value : Union of two tables
    /// Param [table1] : The first table to be operated
    /// Param [table2] : The second table to be operated
    /// </summary>
    class table_union
       : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_TABLE
                || m.GetLocalByID(2).type != VarType.TYPE_TABLE)
                throw new RuntimeException("Invaild argument");
            Variable newTable = new Variable(new Dictionary<Variable, Variable>(VarCompare.GetInstance()));
            var table = newTable.table;
            foreach (var kvp in m.GetLocalByID(2).table)
            {
                table.Add(kvp.Key, kvp.Value);
            }
            foreach (var kvp in m.GetLocalByID(1).table)
            {
                table[kvp.Key] = kvp.Value;
            }
            return newTable;
        }
    }

    /// <summary>
    /// Table.intersect(table1,table2)
    /// table1:intersect(table2)
    /// Intersect two tables, only key/value pairs exist in both table1 and table2 are placed in returned table.
    /// Return Value : Intersect of two tables
    /// Param [table1] : The first table to be operated
    /// Param [table2] : The second table to be operated
    /// </summary>
    class table_intersect
       : IFunction
    {
        public Variable invoke(Machine m, int numParams)
        {
            if (numParams < 2 || m.GetLocalByID(1).type != VarType.TYPE_TABLE
                || m.GetLocalByID(2).type != VarType.TYPE_TABLE)
                throw new RuntimeException("Invaild argument");
            Variable newTable = new Variable(new Dictionary<Variable, Variable>((VarCompare.GetInstance())));
            var table = newTable.table;
            var table2 = m.GetLocalByID(2).table;
            foreach (var kvp in m.GetLocalByID(1).table)
            {
                if (table2.ContainsKey(kvp.Key) && VarCompare.GetInstance().Equals(kvp.Value,table2[kvp.Key]))
                    table.Add(kvp.Key, kvp.Value);
            }
            return newTable;
        }
    }

    /// <summary>
    /// 文件管理器，提供根据句柄来操作文件的一组方法
    /// </summary>
    class FileManager
    {
        private Random random = new Random();
        private IDictionary<double, TextReader> readers
            = new Dictionary<double, TextReader>();
        private IDictionary<double, bool> readerEofs
            = new Dictionary<double, bool>();
        private IDictionary<double, TextWriter> writers
            = new Dictionary<double, TextWriter>();

        /// <summary>
        /// 打开文件
        /// </summary>
        /// <param name="path">文件路径</param>
        /// <param name="writeMode">
        /// 写模式，true表示以写模式打开文件，false表示以读模式打开文件
        /// </param>
        /// <returns>操纵该文件的句柄</returns>
        public double Open(string path, bool writeMode)
        {
            //生成一个与已有句柄不重复的新句柄
            int handle;
            do
            {
                handle = random.Next();
            } while (writers.ContainsKey(handle) || readers.ContainsKey(handle));

            //尝试打开文件
            try
            {
                if (writeMode)
                {
                    TextWriter writer = new StreamWriter(path);
                    writers.Add(handle, writer);
                }
                else
                {
                    TextReader reader = new StreamReader(path);
                    readers.Add(handle, reader);
                    readerEofs.Add(handle, false);
                }
                return handle;
            }
            catch (Exception)
            {
                throw new RuntimeException("Unable to open file : "+ path);
            }
        }

        /// <summary>
        /// 关闭文件
        /// </summary>
        /// <param name="handle">待关闭文件的句柄</param>
        public void Close(double handle)
        {
            try
            {
                if (writers.ContainsKey(handle))
                {
                    writers[handle].Close();
                    writers.Remove(handle);
                    return;
                }

                if (readers.ContainsKey(handle))
                {
                    readers[handle].Close();
                    readers.Remove(handle);
                    readerEofs.Remove(handle);
                    return;
                }
            }
            catch (Exception)
            {
                throw new RuntimeException("File close error.");
            }
            
            //待关闭文件句柄既不在读文件句柄中，也不再写文件句柄中，说明该句柄不存在
            throw new RuntimeException("File handle not exist.");
        }

        /// <summary>
        /// 写文件
        /// </summary>
        /// <param name="handle">待写入文件的句柄</param>
        /// <param name="v">待写入的变量列表</param>
        public void Write(double handle, Variable[] v)
        {
            //确认存在对应文件
            if (!writers.ContainsKey(handle))
                throw new RuntimeException("File handle not exist.");

            TextWriter writer = writers[handle];
            try
            {
                foreach (Variable variable in v)
                {
                    writer.Write(variable.ToString(0));
                }
            }
            catch (Exception)
            {
                throw new RuntimeException("Write file error.");
            }
        }

        /// <summary>
        /// 从文件中读取字符串，以空白字符分开
        /// </summary>
        /// <param name="handle">待读取文件的句柄</param>
        /// <returns>读取到的字符串，若读取失败，则返回空字符串</returns>
        public string ReadString(double handle)
        {
            //确认存在对应文件
            if (!readers.ContainsKey(handle))
                throw new RuntimeException("File handle not exist.");
            TextReader reader = readers[handle];

            Int32 int32ch;
            string s = string.Empty;
            bool isEmpty = true;

            //循环读取直到第一个非空白字符或文件结束
            while (true)
            {
                int32ch = reader.Read();
                if (int32ch < 0) break;
                char ch = (char)int32ch;
                if (!char.IsWhiteSpace(ch))
                {
                    isEmpty = false;
                    s += ch;
                    break;
                }
            }

            //未读到非空白字符，说明文件结束
            if (isEmpty)
            {
                readerEofs[handle] = true;
                return string.Empty;
            }

            //循环读取非空字符并加入字符串，直到空字符或文件结束
            while (true)
            {
                int32ch = reader.Read();
                if (int32ch < 0) break;
                char ch = (char)int32ch;
                if (char.IsWhiteSpace(ch))
                    break;
                else
                    s += ch;
            }
            return s;
        }

        /// <summary>
        /// 判断文件是否已经读取到文件尾
        /// </summary>
        /// <param name="handle">文件句柄</param>
        /// <returns>一个值，表示该文件是否已经读取到文件尾</returns>
        public bool IsEof(double handle)
        {
            if (!readers.ContainsKey(handle))
                throw new RuntimeException("File handle not exist.");
            return readerEofs[handle];
        }

        /// <summary>
        /// 读取数字
        /// </summary>
        /// <param name="handle">待读取文件的句柄</param>
        /// <returns>读取到的数字，若文件已结束则返回0</returns>
        public double ReadNumber(double handle)
        {
            string str = ReadString(handle);
            if (IsEof(handle)) return 0;

            try
            {
                return double.Parse(str);
            }
            catch (Exception)
            {
                throw new RuntimeException("Cannot Convert to Number!");
            }
        }

        /// <summary>
        /// 读取当前行
        /// </summary>
        /// <param name="handle"></param>
        /// <returns>
        /// 当前行所对应的字符串，不包括换行符。若当前行不存在（文件已读完）则返回空字符串。
        /// </returns>
        public string ReadLine(double handle)
        {
            if (!readers.ContainsKey(handle))
                throw new RuntimeException("File handle not exist.");
            TextReader reader = readers[handle];

            try
            {
                string s = reader.ReadLine();
                //文件已读完
                if (s == null)
                {
                    readerEofs[handle] = true;
                    s = "";
                }
                return s;
            }
            catch (Exception)
            {
                throw new RuntimeException("I/O Error: Unable to readline");
            }
        }
    }

    /// <summary>
    /// 文件打开函数，根据提供的路径打开文件
    /// </summary>
    class file_open
        : IFunction
    {
        FileManager m_manager;
        bool m_write;

        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="manager">文件管理器</param>
        /// <param name="write">是否使用写模式打开文件</param>
        public file_open(FileManager manager, bool write)
        {
            m_manager = manager;
            m_write = write;
        }

        /// <summary>
        /// 打开文件，由解释器调用
        /// </summary>
        /// <param name="m">虚拟机</param>
        /// <param name="numParams">函数调用参数数目</param>
        /// <returns>生成的文件类</returns>
        public Variable invoke(Machine m, int numParams)
        {
            //第一个参数需为字符串，表示文件路径
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_STRING)
                throw new RuntimeException("Invalid Argument");

            //生成的文件类
            Variable v = new Variable(new Dictionary<Variable, Variable>(VarCompare.GetInstance()));
            //打开文件，并获得句柄
            double handle = m_manager.Open(m.GetLocalByID(1).str, m_write);

            if (m_write)
            {
                //以写方式打开文件，提供write函数
                v.table[new Variable("write")] = new Variable(new file_write(m_manager));
            }
            else
            {
                //以读方式打开文件，提供read_string, read_number和read_file和eof函数
                v.table[new Variable("read_string")] =
                    new Variable(new file_read(m_manager, file_read.ReturnType.String));
                v.table[new Variable("read_number")] =
                    new Variable(new file_read(m_manager, file_read.ReturnType.Number));
                v.table[new Variable("read_line")] =
                    new Variable(new file_read(m_manager, file_read.ReturnType.Line));
                v.table[new Variable("eof")] =
                    new Variable(new file_eof(m_manager));
            }
            
            //无论是否以读还是写方式打开文件，都提供close函数和handle属性
            v.table[new Variable("close")] = new Variable(new file_close(m_manager));
            v.table[new Variable("handle")] = new Variable(handle);
            return v;
        }
    }

    /// <summary>
    /// 提供文件类，判断文件是否已经读到文件尾
    /// </summary>
    class file_eof
        : IFunction
    {
        FileManager m_manager;
        
        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="manager">文件管理器</param>
        public file_eof(FileManager manager)
        {
            m_manager = manager;
        }

        /// <summary>
        /// 查询某个文件是否已经读到文件尾，解释器调用
        /// </summary>
        /// <param name="m">虚拟机</param>
        /// <param name="numParams">函数调用参数数目</param>
        /// <returns>查询结果，数值类型，1表示已读到文件尾，0表示未读到文件尾</returns>
        public Variable invoke(Machine m, int numParams)
        {
            //第一个参数须是一个表，并且存在handle域，并且handle域应为数值类型
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_TABLE ||
                !m.GetLocalByID(1).table.ContainsKey(new Variable("handle")) ||
                m.GetLocalByID(1).table[new Variable("handle")].type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invalid Argument");

            bool eof = m_manager.IsEof(m.GetLocalByID(1).table[new Variable("handle")].num);
            if (eof)
                return new Variable(1.0);
            else
                return new Variable(0.0);
        }
    }

    /// <summary>
    /// 提供文件类，关闭对应的文件
    /// </summary>
    class file_close
        : IFunction
    {
        FileManager m_manager;

        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="manager">文件管理器</param>
        public file_close(FileManager manager)
        {
            m_manager = manager;
        }

        /// <summary>
        /// 关闭文件，由解释器调用
        /// </summary>
        /// <param name="m">虚拟机</param>
        /// <param name="numParams">函数调用参数数目</param>
        /// <returns>数值0</returns>
        public Variable invoke(Machine m, int numParams)
        {
            //第一个参数须是一个表，并且存在handle域，并且handle域应为数值类型
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_TABLE ||
                !m.GetLocalByID(1).table.ContainsKey(new Variable("handle")) ||
                m.GetLocalByID(1).table[new Variable("handle")].type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invalid Argument");

            m_manager.Close(m.GetLocalByID(1).table[new Variable("handle")].num);
            return new Variable(0.0);
        }
    }

    /// <summary>
    /// 提供文件类和待写入的变量，向文件中写入变量
    /// </summary>
    class file_write
        : IFunction
    {
        FileManager m_manager;

        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="manager">文件管理器</param>
        public file_write(FileManager manager)
        {
            m_manager = manager;
        }

        /// <summary>
        /// 写文件，由解释器调用
        /// </summary>
        /// <param name="m">虚拟机</param>
        /// <param name="numParams">函数调用参数数目</param>
        /// <returns>数值0</returns>
        public Variable invoke(Machine m, int numParams)
        {
            //第一个参数须是一个表，并且存在handle域，并且handle域应为数值类型
            //以后的参数可以为任意多，并且可以是任意类型，表示待写入的参数
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_TABLE ||
                !m.GetLocalByID(1).table.ContainsKey(new Variable("handle")) ||
                m.GetLocalByID(1).table[new Variable("handle")].type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invalid Argument");

            Variable[] vars = new Variable[numParams - 1];
            for (int i = 2; i <= numParams; ++i)
                vars[i - 2] = (m.GetLocalByID(i));

            m_manager.Write(m.GetLocalByID(1).table[new Variable("handle")].num, vars);
            return new Variable(0.0);
        }
    }

    /// <summary>
    /// 从文件类中读取信息
    /// </summary>
    class file_read
        : IFunction
    {
        /// <summary>
        /// 读取方式类型，分别是读取数值，读取字符串和读取整行
        /// </summary>
        public enum ReturnType
        {
            Number,
            String,
            Line
        }
        ReturnType m_returnType;
        FileManager m_manager;

        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="manager">文件管理器</param>
        /// <param name="returnType">读取类型</param>
        public file_read(FileManager manager, ReturnType returnType)
        {
            m_returnType = returnType;
            m_manager = manager;
        }

        /// <summary>
        /// 读文件，由解释器调用
        /// </summary>
        /// <param name="m">虚拟机</param>
        /// <param name="numParams">函数调用参数数目</param>
        /// <returns>被读取的变量，类型有读取类型决定</returns>
        public Variable invoke(Machine m, int numParams)
        {
            //第一个参数须是一个表，并且存在handle域，并且handle域应为数值类型
            if (numParams < 1 || m.GetLocalByID(1).type != VarType.TYPE_TABLE ||
                !m.GetLocalByID(1).table.ContainsKey(new Variable("handle")) ||
                m.GetLocalByID(1).table[new Variable("handle")].type != VarType.TYPE_NUMBER)
                throw new RuntimeException("Invalid Argument");

            switch (m_returnType)
            {
                case ReturnType.String:
                    return new Variable(m_manager.ReadString(
                        m.GetLocalByID(1).table[new Variable("handle")].num));
                case ReturnType.Number:
                    return new Variable(m_manager.ReadNumber(
                        m.GetLocalByID(1).table[new Variable("handle")].num));
                case ReturnType.Line:
                    return new Variable(m_manager.ReadLine(
                        m.GetLocalByID(1).table[new Variable("handle")].num));
            }
            return new Variable();
        }
    }

    /// <summary>
    /// 库函数集合
    /// </summary>
    static class Library
    {
        /// <summary>
        /// Open library, register all library functions for a given Machine
        /// </summary>
        /// <param name="m">The given Machine</param>
        static public void OpenLibrary(Machine m)
        {
            Variable v = new Variable(new Dictionary<Variable, Variable>(VarCompare.GetInstance()));

            v.table[new Variable("concat")] = new Variable(new string_concat());
            v.table[new Variable("compare")] = new Variable(new string_compare());
            v.table[new Variable("contain")] = new Variable(new string_contain());
            v.table[new Variable("length")] = new Variable(new string_length());
            v.table[new Variable("match")] = new Variable(new string_match());
            v.table[new Variable("from")] = new Variable(new string_from());
            v.table[new Variable("sub")] = new Variable(new string_sub());
            //String library
            m.SetGlobal("String", v);

            v = new Variable(new Dictionary<Variable, Variable>(VarCompare.GetInstance()));
            //Math library
            v.table[new Variable("sin")] = new Variable(new math_sin());
            v.table[new Variable("cos")] = new Variable(new math_cos());
            v.table[new Variable("abs")] = new Variable(new math_abs());
            v.table[new Variable("ceiling")] = new Variable(new math_ceiling());
            v.table[new Variable("floor")] = new Variable(new math_floor());
            v.table[new Variable("exp")] = new Variable(new math_exp());
            v.table[new Variable("pow")] = new Variable(new math_pow());
            v.table[new Variable("log")] = new Variable(new math_log());
            v.table[new Variable("random")] = new Variable(new math_random());
            m.SetGlobal("Math", v);

            v = new Variable(new Dictionary<Variable, Variable>(VarCompare.GetInstance()));
            //Table library
            v.table[new Variable("copy")] = new Variable(new table_copy());
            v.table[new Variable("contain_key")] = new Variable(new table_contain_key());
            v.table[new Variable("contain_value")] = new Variable(new table_contain_value());
            v.table[new Variable("iterator")] = new Variable(new table_iterator());
            v.table[new Variable("intersect")] = new Variable(new table_intersect());
            v.table[new Variable("union")] = new Variable(new table_union());
            v.table[new Variable("insert")] = new Variable(new table_insert());
            v.table[new Variable("remove")] = new Variable(new table_remove());
            m.SetGlobal("Table", v);

            v = new Variable(new Dictionary<Variable, Variable>(VarCompare.GetInstance()));
            FileManager fm = new FileManager();
            //File library
            v.table[new Variable("open_write")] = new Variable(new file_open(fm, true));
            v.table[new Variable("open_read")] = new Variable(new file_open(fm, false));

            m.SetGlobal("File", v);
        }

    }
}
